/*
 ***************************************************************************************
 *  Copyright (C) 2006 EsperTech, Inc. All rights reserved.                            *
 *  http://www.espertech.com/esper                                                     *
 *  http://www.espertech.com                                                           *
 *  ---------------------------------------------------------------------------------- *
 *  The software in this package is published under the terms of the GPL license       *
 *  a copy of which has been included with this distribution in the license.txt file.  *
 ***************************************************************************************
 */
package com.espertech.esper.common.internal.epl.join.queryplanouter;

import com.espertech.esper.common.internal.collection.InterchangeablePair;
import com.espertech.esper.common.internal.compile.stage1.spec.OuterJoinDesc;
import com.espertech.esper.common.internal.type.OuterJoinType;

import java.util.HashSet;
import java.util.Set;

public class InnerJoinGraph {

    private final int numStreams;
    private final boolean isAllInnerJoin;
    private final Set<InterchangeablePair<Integer, Integer>> innerJoins;

    public InnerJoinGraph(int numStreams, Set<InterchangeablePair<Integer, Integer>> innerJoins) {
        this.numStreams = numStreams;
        this.isAllInnerJoin = false;
        this.innerJoins = innerJoins;
    }

    public InnerJoinGraph(int numStreams, boolean isAllInnerJoin) {
        this.numStreams = numStreams;
        this.isAllInnerJoin = isAllInnerJoin;
        this.innerJoins = null;
    }

    public boolean isAllInnerJoin() {
        return isAllInnerJoin;
    }

    public boolean isEmpty() {
        if (isAllInnerJoin) {
            return false;
        }
        return innerJoins.isEmpty();
    }

    public boolean hasInnerJoin(int toStream) {
        if (isAllInnerJoin) {
            return true;
        }
        boolean hasInnerJoin = false;
        for (InterchangeablePair<Integer, Integer> pair : innerJoins) {
            if (pair.getFirst() == toStream) {
                hasInnerJoin = true;
            }
            if (pair.getSecond() == toStream) {
                hasInnerJoin = true;
            }
        }
        return hasInnerJoin;
    }

    public static InnerJoinGraph graphInnerJoins(int numStreams, OuterJoinDesc[] outerJoinDescList) {
        if ((outerJoinDescList.length + 1) != numStreams) {
            throw new IllegalArgumentException("Number of outer join descriptors and number of streams not matching up");
        }

        Set<InterchangeablePair<Integer, Integer>> graph = new HashSet<InterchangeablePair<Integer, Integer>>();

        boolean allInnerJoin = true;
        for (int i = 0; i < outerJoinDescList.length; i++) {
            OuterJoinDesc desc = outerJoinDescList[i];
            int streamMax = i + 1;       // the outer join must references streams less then streamMax

            // Check outer join on-expression, if provided
            if (desc.getOptLeftNode() != null) {
                int streamOne = desc.getOptLeftNode().getStreamId();
                int streamTwo = desc.getOptRightNode().getStreamId();

                if ((streamOne > streamMax) || (streamTwo > streamMax) ||
                        (streamOne == streamTwo)) {
                    throw new IllegalArgumentException("Outer join descriptors reference future streams, or same streams");
                }

                if (desc.getOuterJoinType() == OuterJoinType.INNER) {
                    graph.add(new InterchangeablePair<Integer, Integer>(streamOne, streamTwo));
                }
            }

            if (desc.getOuterJoinType() != OuterJoinType.INNER) {
                allInnerJoin = false;
            }
        }

        if (allInnerJoin) {
            return new InnerJoinGraph(numStreams, true);
        }
        return new InnerJoinGraph(numStreams, graph);
    }

    public void addRequiredStreams(int streamNum, Set<Integer> requiredStreams, Set<Integer> completedStreams) {
        if (isAllInnerJoin) {
            for (int i = 0; i < numStreams; i++) {
                if (!completedStreams.contains(i)) {
                    requiredStreams.add(i);
                }
            }
            return;
        }

        for (InterchangeablePair<Integer, Integer> pair : innerJoins) {
            if (pair.getFirst() == streamNum) {
                if (!completedStreams.contains(pair.getSecond())) {
                    requiredStreams.add(pair.getSecond());
                }
            }
            if (pair.getSecond() == streamNum) {
                if (!completedStreams.contains(pair.getFirst())) {
                    requiredStreams.add(pair.getFirst());
                }
            }
        }
    }
}
